import {observable, action, toJS, isObservable} from 'mobx';
import * as moment from 'moment';
import _ from 'lodash';

type ValidateStatus = 'success'|'warning'|'error'|'validating';

export class FieldItem {

    constructor(values?: FieldItem) {
        _.forEach(values, (value, key: string) => {
            if (this.hasOwnProperty(key)) {
                this[key] = value;
            }
        })
    }

    value?: any = null;
    help?: string = '';
    valid?: boolean = true;
    disabled?: boolean = false;
    validateStatus?: any  = '';
    hasFeedback?: boolean = false;
    required?: boolean = false;
    ignore?: boolean = false;
    option?: Array<any> = [];
}

export class FormStore<FieldsType,OriginalType> {

    defaultFields;

    fields: FieldsType;

    validators: Object = {};

    @observable visible = false;

    @action changeField(name, values: FieldItem): FieldItem {
        let field = toJSDeep(this.fields[name]);
        return this.fields[name] = _.assign(field, values);
    }

    @action changeFieldWidthVaild(name, field: FieldItem){

        let value = field.value ? field.value : toJSDeep(this.fields[name].value);
        
        let valid = this.valid(name, value, !this.fields[name].required, false);

        return this.changeField(name,_.assign({
            valid: valid
        } ,field))
    }

    @action changeFields(newFields): FieldsType {
        let fields: Object = toJSDeep(this.fields);
        return this.fields = _.mapValues(fields, (val, key) => {
            return _.assign(val, newFields[key])
        })
    }

    @action show(values?: OriginalType) {
        if (this.visible === false) {
            if (values) {
                this.changeFields(this.createField(values));
            }
        }

        setTimeout(() => {
            this.visible = true;
        }, 100)
    }

    @action hide() {
        if (this.visible) {
            this.visible = false;
            setTimeout(() => {
                this.clearAll();
            }, 100)
        }
    }

    @action valid(fieldName: string, value: any, allowEmpty = true, changeStatus = true): FieldItem {
        let res: FieldItem = {
            valid: true,
            help: '',
            validateStatus: 'success'
        };
        if (isObservable(value)) {
            value = toJSDeep(value);
        }
        res.value = value;
        if (this.validators.hasOwnProperty(fieldName) && this.fields[fieldName].ignore !== true) {
            if (!(allowEmpty && _.isEmpty(value))) {
                res.valid = _.every(this.validators[fieldName], function (fn) {
                    let helpOrValid: string | boolean = fn(value);
                    let valid = true;
                    if (typeof helpOrValid === 'string') {
                        res.help = helpOrValid;
                        res.validateStatus = 'error';
                        valid = false;
                    }
                    return valid;
                });
            }
        }
        if (changeStatus) {
            this.changeField(fieldName, res);
        }
        return res;
    }

    @action isValid(): boolean {
        const res = _.mapValues(this.fields, (field: FieldItem, name) => {
            return this.valid(name, field.value, false, false);
        });
        const isV = _.every(res, (item) => {
            return item.valid;
        });
        this.changeFields(res);
        return isV;
    }

    @action async asyncValid(fieldName: string, value: any, allowEmpty = true, changeStatus = true): Promise<FieldItem> {
        let res: FieldItem = {
            valid: true,
            help: '',
            validateStatus: 'success'
        };
        if (isObservable(value)) {
            value = toJSDeep(value);
        }
        res.value = value;

        if (this.validators.hasOwnProperty(fieldName) && this.fields[fieldName].ignore !== true) {
            if (!(allowEmpty && _.isEmpty(value))) {
                for await (const fn of this.validators[fieldName]){
                    let helpOrValid = await fn(value);

                    if(typeof helpOrValid === 'string'){
                        res.help = helpOrValid;
                        res.validateStatus = 'error';
                        res.valid = false;
                        break;
                    }

                }
            }
        }

        if (changeStatus) {
            this.changeField(fieldName, res);
        }

        return res;
    }

    @action async isAsyncValid(): Promise<boolean> {

        const fields = this.fields;
        const fieldsArray = _.keys(fields); 
        let isV = true;
        let res :any = {};
        
        for await (const name of fieldsArray){
            let field: FieldItem = fields[name];
            let validRes = await this.asyncValid(name, field.value, false, false);
            if(validRes.valid === false){
                isV = false;
            }
            res[name] = validRes;
        }

        this.changeFields(res);
        return isV;
    }

    @action clear(name: string): boolean {
        if (this.fields.hasOwnProperty(name)) {
            this.fields[name] = _.cloneDeep(this.defaultFields[name]);
            return this.fields[name]
        } else {
            return
        }
    }

    @action clearAll(): FieldsType {
        return this.fields = _.cloneDeep(this.defaultFields);
    }

    getValues(): OriginalType {
        return _.mapValues(this.fields, (field: FieldItem) => {
            return toJS(field.value);
        });
    }

    createField(values?: OriginalType){
        return _.assign(_.cloneDeep(this.defaultFields), _.mapValues(values, (value,key) => {
            return {value: value}
        }))
    }

    unionValue(value, field: FieldItem) {

        field.value = toString(value);
        if (value >= 0 && field.value !== '-1') {
            field.disabled = false;
        }

        return field;
    }

    moment(value, field: FieldItem) {
        if (_.isEmpty(value) === false && typeof value === 'string') {
            field.value = moment(value, 'YYYY-MM-DD HH:mm:ss')
        }
        return field
    }

    momentToString(value) {
        if (typeof isRequired(value) === 'string' || typeof value === 'string') {
            return value;
        } else if (value.format) {
            return value.format('YYYY-MM-DD HH:mm:ss')
        }
    }

}

function toString(value) {
    return value + '';
}

export function isRequired(value) {
    if (_.isUndefined(value) || _.isNull(value) || _.isEmpty(_.toString(value))) {
        return '必填项'
    } else {
        return true;
    }
}

export function isSelectRequired(value) {
    if (_.isUndefined(value) || _.isNull(value) || _.toString(value) === '-1') {
        return '必填项'
    } else {
        return true;
    }
}

export function toJSDeep(source, detectCycles: boolean = false) {
    const clone = toJS(source, detectCycles);
    _.forEach(clone, function (value, key) {
        if (isObservable(value)) {
            clone[key] = toJSDeep(value, detectCycles);
        }
    });
    return clone;
}